/*
Copyright (C) 2022-2024 Inspur Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package service

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/go-playground/validator/v10"
	"github.com/go-xorm/xorm"
	"github.com/pkg/errors"

	"did-service/bean/entity"
	"did-service/bean/model"
	"did-service/bean/param"
	"did-service/bean/view"
	"did-service/chain/client"
	chainModel "did-service/chain/model"
	"did-service/common/constant"
	"did-service/common/crypto/gm"
	"did-service/common/utils"
	"did-service/common/uuid"
	"did-service/config"
)

const (
	ErrorInfoTag = "errorInfo"
)

// ValidatePara 验证请求参数
func ValidatePara(s interface{}) (bool, string) {
	validate := validator.New()
	_ = validate.RegisterValidation("checkDate", checkDateFunc)
	if errs := validate.Struct(s); errs != nil {
		errStr := paraErrorInfo(s, errs)
		if errStr != nil {
			return false, strings.Join(errStr, ",")
		}
		return false, ""
	}
	return true, ""
}

// 自定义校验日期格式的函数
func checkDateFunc(fl validator.FieldLevel) bool {
	if _, err := time.Parse(constant.TimeLayout, fl.Field().String()); err != nil {
		return false
	}
	return true
}

// validator参数校验的错误提示
func paraErrorInfo(s interface{}, err error) []string {
	errs := make([]string, 0)
	//输入参数无效，则直接返回输入参数错误
	invalid, ok := err.(*validator.InvalidValidationError)
	if ok {
		log.Errorf("validate para err: %s", invalid.Error())
		return []string{"输入参数错误"}
	}

	validationErrs, ok := err.(validator.ValidationErrors)
	if ok {
		for _, validationErr := range validationErrs {
			//获取字段名称
			fieldName := validationErr.Field()
			typeOf := reflect.TypeOf(s)
			// 如果是指针，获取其属性
			if typeOf.Kind() == reflect.Ptr {
				typeOf = typeOf.Elem()
			}
			//获取filed
			field, ok := typeOf.FieldByName(fieldName)
			if ok {
				// 获取field对应的errorInfo tag值
				errorInfo := field.Tag.Get(ErrorInfoTag)
				paraName := field.Tag.Get("json")
				if errorInfo != "" {
					errs = append(errs, paraName+":"+errorInfo)
				} else {
					errs = append(errs, paraName+":不能为空")
				}
			}
		}
		return errs
	}
	return nil
}

// ValidateIssuerStatus 验证发证方状态(1:审核通过并可用)
func (ds *DidService) ValidateIssuerStatus(Id string) (IssuerDid, IssuerWalletId string, err error) {
	issuer := new(entity.AuthorityIssuer)
	ok, err := ds.engine.Where("id = ?", Id).Get(issuer)
	if err != nil {
		return "", "", nil
	}
	if !ok {
		return "", "", errors.New("the issuer does not exist")
	}
	if issuer.Status != constant.IssuerStatusOk {
		return "", "", errors.New("the issuer is unable to issue the credential at present")
	}
	return issuer.Did, issuer.WalletId, nil
}

// ValidateIssuerIdAndCptId 验证请求中的发证方id和cptId是否匹配
func (ds *DidService) ValidateIssuerIdAndCptId(id, cptId string) error {
	cpt := new(entity.CredentialCpt)
	ok, err := ds.engine.Where("id = ? and publisher_id = ?", cptId, id).Get(cpt)
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("cpt does not exist")
	}
	return nil
}

// ValidateCptClaim 验证CptClaim中的属性
func (ds *DidService) ValidateCptClaim(cptId string, credSubject model.CredentialSubject) error {
	claimMap, _, totalNum, err := getCptClaim(ds.engine, cptId)
	if err != nil {
		return err
	}
	claimJson, err := simplejson.NewJson([]byte(credSubject.Claims))
	if err != nil {
		return err
	}
	jsonMap, err := claimJson.Map()
	if err != nil {
		return err
	}
	//校验属性是否有效
	validNum := 0
	for key := range jsonMap {
		claim := claimMap[key]
		//属性不存在或无效
		if claim == nil || claim.Status != constant.CptClaimStatusValid {
			return errors.New("invalid cpt claim")
		}
		validNum += 1
	}
	//
	if validNum != totalNum {
		return errors.New("missing necessary properties")
	}
	return nil
}

// ValidateCredSubject 验证请求中的CredSubject.id是否归属于当前用户
func (ds *DidService) ValidateCredSubject(walletId string, credSub model.CredentialSubject) error {
	dids, err := walletId2MultiDid(ds.engine, walletId)
	if err != nil {
		return err
	}
	for _, did := range dids {
		if did == credSub.Id {
			return nil
		}
	}
	return errors.New("the credSubject.id does not belong to the current user")
}

// GenerateCredential 生成可验证凭证
func (ds *DidService) GenerateCredential(credId, IssuerId, issuerDid string, para param.ApplyCredentialParam, ownerWalletId string) error {

	expiredDays := config.GetInt("credential.expired-days", 30)

	protocol := getRevocationConf()

	//查询凭证吊销列表的地址
	revocationUrl := fmtRevocationUrl(protocol, constant.CredentialRevocationUrlSuffix)

	cred := &model.Credential{
		Context:           []string{constant.CredentialContext},
		CptId:             para.CptId,
		Id:                credId,
		Type:              para.Type,
		Issuer:            issuerDid,
		IssuanceDate:      time.Now().Format(constant.TimeLayout),
		ExpirationDate:    time.Now().Add(time.Hour * 24 * time.Duration(expiredDays)).Format(constant.TimeLayout),
		CredentialSubject: para.CredentialSubject,
		Revocation:        model.VcRevocation{Id: revocationUrl, Type: constant.TypeSimpleRevocationListV1},
		Proof:             model.VcProof{},
	}
	//获取发证方私钥
	prvKey, keyId, keyType, err := getPrvKey(ds.engine, issuerDid, 1)
	if err != nil {
		return err
	}

	cred.Proof.Creator = keyId
	cred.Proof.Type = keyType

	credBytes, err := json.Marshal(cred)
	if err != nil {
		return err
	}
	//对凭证签名
	credSign := gm.Sign(prvKey, credBytes)
	if credSign == "" {
		return errors.New("failed to sign credential data")
	}
	cred.Proof.SignatureValue = credSign

	chainClient := ds.getChainClient()
	//上链
	txId, err := credOnChain(chainClient, cred)
	if err != nil {
		return err
	}

	//插入凭证和凭证属性表
	if err = insertCredAndClaim(ds.engine, cred, IssuerId, ownerWalletId, txId); err != nil {
		return err
	}

	return nil
}

// 获取Revocation Url配置
func getRevocationConf() (protocol string) {
	protocol = config.Get("did.endpoint", constant.DefaultServiceResolveEndpoint)
	return
}

func fmtRevocationUrl(protocol, suffix string) string {
	return fmt.Sprintf("%s%s", protocol, suffix)
}

func getPrvKey(engine *xorm.Engine, did string, keyNum int) (prvKey, keyId, keyType string, err error) {
	if keyNum == 1 {
		keyId = did + constant.DefaultKey1Subfix
	} else {
		keyId = did + constant.DefaultKey2Subfix
	}

	docKey := make([]*entity.DidDocumentKey, 0)
	if err = engine.Where("key_id = ?", keyId).Find(&docKey); err != nil {
		return
	}
	if len(docKey) == 0 {
		return "", "", "", errors.New("key does not exist")
	}
	return docKey[0].PrvKey, strings.ReplaceAll(keyId, "#keys-", "/"), docKey[0].Type, nil
}

func insertCredAndClaim(engine *xorm.Engine, cred *model.Credential, IssuerId, ownerWalletId, txId string) error {
	//查询模板属性表
	_, claims, _, err := getCptClaim(engine, cred.CptId)
	if err != nil {
		return err
	}

	credClaim, err := parseCredClaim(cred, claims)
	if err != nil {
		return err
	}
	credSubjectBytes, err := json.Marshal(cred.CredentialSubject)
	if err != nil {
		return err
	}
	credSubjectStr := string(credSubjectBytes)

	verifiCred := entity.VerifiableCredential{
		Id:                cred.Id,
		Type:              constant.CptTypeLite,
		CptId:             cred.CptId,
		IssuerId:          IssuerId,
		IssuerDid:         cred.Issuer,
		OwnerId:           ownerWalletId,
		OwnerDid:          cred.CredentialSubject.Id,
		IssuanceDate:      cred.IssuanceDate,
		ExpirationDate:    cred.ExpirationDate,
		ChainStoreType:    constant.CredentialOnChainTypeHash,
		CredentialSubject: credSubjectStr,
		RevocationId:      cred.Revocation.Id,
		RevocationType:    constant.TypeSimpleRevocationListV1,
		ProofCreator:      cred.Proof.Creator,
		ProofType:         constant.SignTypeSm2p256v1,
		ProofSignature:    cred.Proof.SignatureValue,
		Status:            constant.CredentialStatusValid,
		TxId:              txId,
		CreatedTime:       time.Now(),
		UpdatedTime:       time.Now(),
	}
	//插入可验证凭证属性表
	if _, err = engine.Insert(&credClaim); err != nil {
		return err
	}
	//插入可验证凭证表
	if _, err = engine.InsertOne(&verifiCred); err != nil {
		return err
	}
	return nil
}

// 将凭证上链
func credOnChain(chainClient *client.IchainClient, cred *model.Credential) (string, error) {
	if chainClient == nil {
		return "", errors.New("failed to get chain Client")
	}
	credChain := chainModel.CredentialInfo{
		Id:        cred.Id,
		IssuerDid: cred.Issuer,
		CptId:     cred.CptId,
		OwnerDid:  cred.CredentialSubject.Id,
		ClaimHash: gm.Sm3(cred.CredentialSubject.Claims),
	}

	txId, _ := chainClient.SaveCredentialInfo(&credChain)
	return txId, nil
}

// 查询模板属性表 返回有效的属性
func getCptClaim(engine *xorm.Engine, cptId string) (claimMap map[string]*entity.CredentialCptClaim, cptClaims []*entity.CredentialCptClaim,
	validPropertyNum int, err error) {
	tempCptClaims := make([]*entity.CredentialCptClaim, 0)
	if err = engine.Where("cpt_id = ?", cptId).Find(&tempCptClaims); err != nil {
		return nil, nil, 0, err
	}
	if len(tempCptClaims) == 0 {
		return nil, nil, 0, errors.New("cptId does not exist")
	}
	claimMap = make(map[string]*entity.CredentialCptClaim, 0)
	cptClaims = make([]*entity.CredentialCptClaim, 0)
	validPropertyNum = 0

	for _, cptClaim := range tempCptClaims {
		//选取有效属性, key为属性名
		if cptClaim.Status == constant.CptClaimStatusValid {
			claimMap[cptClaim.Property] = cptClaim
			cptClaims = append(cptClaims, cptClaim)
			validPropertyNum += 1
		}
	}
	return claimMap, cptClaims, validPropertyNum, nil
}

// 根据credentialCptClaims中的属性解析CredentialSubject中的Claims
func parseCredClaim(cred *model.Credential, cptClaims []*entity.CredentialCptClaim) ([]*entity.VerifiableCredentialClaim, error) {
	credClaims := make([]*entity.VerifiableCredentialClaim, 0)

	claimJson, err := simplejson.NewJson([]byte(cred.CredentialSubject.Claims))
	if err != nil {
		return nil, err
	}
	for _, cptClaim := range cptClaims {
		var val string
		switch cptClaim.ValueType {
		case constant.CptClaimValueTypeString, constant.CptClaimValueTypeDate:
			val, err = claimJson.Get(cptClaim.Property).String()
		case constant.CptClaimValueTypeNum:
			var intVal int64
			intVal, err = claimJson.Get(cptClaim.Property).Int64()
			val = strconv.FormatInt(intVal, 10)
		case constant.CptClaimValueTypeBool:
			var boolVal bool
			boolVal, err = claimJson.Get(cptClaim.Property).Bool()
			val = strconv.FormatBool(boolVal)
		}
		if err != nil {
			return nil, err
		}

		credClaim := &entity.VerifiableCredentialClaim{
			//TODO 声明id(主键)
			Id:           uuid.Generate(),
			CredentialId: cred.Id,
			Property:     cptClaim.Property,
			Value:        val,
			ValueType:    cptClaim.ValueType,
			Seq:          cptClaim.Seq,
			CreatedTime:  time.Now(),
			UpdatedTime:  time.Now(),
		}
		credClaims = append(credClaims, credClaim)
	}
	return credClaims, nil
}

// CheckCredIsExistence 检查凭证是否存在
func (ds *DidService) CheckCredIsExistence(cptId, issuerDid, ownerDid string) (string, error) {
	cred := new(entity.VerifiableCredential)
	sqlStr := "cpt_id = ? and issuer_did = ? and owner_did = ? and status != ?"
	//根据cptId、发证方did和所有者did查询
	ok, err := ds.engine.Where(sqlStr, cptId, issuerDid, ownerDid, constant.CredentialStatusInvalid).Get(cred)
	if err != nil {
		return "", err
	}
	if ok {
		return cred.Id, nil
	}
	return "", nil
}

// CheckCredIsExistenceById 根据id检查凭证是否存在、是否无效
func (ds *DidService) CheckCredIsExistenceById(credId string) (exist, valid bool, err error) {
	cred := new(entity.VerifiableCredential)

	ok, err := ds.engine.Where("id = ?", credId).Get(cred)
	if err != nil {
		return false, false, err
	}
	if ok {
		return true, cred.Status != constant.CredentialStatusInvalid, nil
	}
	return false, false, nil
}

// CheckCredIsRevoked 检查是否被撤销
func (ds *DidService) CheckCredIsRevoked(credId string) (bool, *entity.RevokedCredential, error) {
	revokedCred := make([]*entity.RevokedCredential, 0)
	if err := ds.engine.Where("credential_id = ?", credId).Find(&revokedCred); err != nil {
		return false, nil, err
	}
	if len(revokedCred) == 0 {
		return false, nil, nil
	}
	return true, revokedCred[0], nil
}

// RevokeCredential 吊销凭证
func (ds *DidService) RevokeCredential(walletId string, para param.RevokeCredentialParam) error {
	issuerStatus, err := issuerWalletId2MultiDid(ds.engine, walletId)
	if err != nil {
		return err
	}
	//验证凭证是否为指定的did签发
	cptId, did, ok, err := verifyCredIssuer(ds.engine, para.CredentialId, issuerStatus)
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("issuer did not issue the credential")
	}

	revokeDate := time.Now()
	//使用主秘钥签名
	prvKey, _, _, err := getPrvKey(ds.engine, did, 1)
	if err != nil {
		return err
	}
	sign := gm.Sign(prvKey, []byte(para.CredentialId+revokeDate.Format(constant.TimeLayout)))
	if sign == "" {
		return errors.New("revoke Sign error")
	}

	revokedCred := entity.RevokedCredential{
		Id:              uuid.Generate(),
		CredentialId:    para.CredentialId,
		CptId:           cptId,
		Issuer:          did,
		RevokeDate:      revokeDate,
		RevokeReason:    para.RevokeReason,
		RevokeSignature: sign,
		CreatedTime:     time.Now(),
		UpdatedTime:     time.Now(),
	}
	//插入撤销凭证表
	if _, err = ds.engine.Insert(&revokedCred); err != nil {
		return err
	}
	//更新凭证的状态
	_, err = ds.engine.Where("id = ?", para.CredentialId).Cols("status").
		Update(&entity.VerifiableCredential{Status: constant.CredentialStatusInvalid})
	if err != nil {
		return err
	}

	return nil
}

// 获取walletId对应的did
func walletId2Did(engine *xorm.Engine, walletId string) (did string, err error) {
	docDoc := make([]*entity.DidDocument, 0)
	if err = engine.Where("wallet_id = ?", walletId).Find(&docDoc); err != nil {
		return
	}
	if len(docDoc) == 0 {
		return "", errors.New("walletId does not exist")
	}
	return docDoc[0].Did, nil
}

// issuerWalletId2MultiDid 获取发证方walletId对应的did(多个)
func issuerWalletId2MultiDid(engine *xorm.Engine, walletId string) (statusMap map[string]string, err error) {
	issuers := make([]*entity.AuthorityIssuer, 0)
	statusMap = make(map[string]string, 0)
	if err = engine.Where("wallet_id = ?", walletId).Find(&issuers); err != nil {
		return
	}
	if len(issuers) == 0 {
		return nil, errors.New("walletId is not issuer")
	}
	for _, issuer := range issuers {
		statusMap[issuer.Did] = issuer.Status
	}
	return
}

// 获取walletId对应的did(多个)
func walletId2MultiDid(engine *xorm.Engine, walletId string) (did []string, err error) {
	docDoc := make([]*entity.DidDocument, 0)
	if err = engine.Where("wallet_id = ?", walletId).Find(&docDoc); err != nil {
		return
	}
	if len(docDoc) == 0 {
		return nil, errors.New("walletId does not exist")
	}
	for _, i := range docDoc {
		did = append(did, i.Did)
	}
	return
}

// 验证凭证是否为指定的did签发
func verifyCredIssuer(engine *xorm.Engine, credId string, statusMap map[string]string) (cptId,
	IssuerDid string, ok bool, err error) {

	verCred := make([]*entity.VerifiableCredential, 0)
	if err = engine.Where("id = ?", credId).Find(&verCred); err != nil {
		return "", "", false, err
	}
	if len(verCred) == 0 {
		return "", "", false, errors.New("credential does not exist")
	}
	for did, _ := range statusMap {
		if verCred[0].IssuerDid == did {
			if statusMap[did] != "" && statusMap[did] != constant.IssuerStatusOk {
				return "", "", false, errors.New("the issuer did has been deactivated")
			}
			return verCred[0].CptId, did, true, nil
		}
	}
	return "", "", false, nil
}

// AcquireCredential 获取凭证
func (ds *DidService) AcquireCredential(credId, walletId string) (*view.CredentialResp, error) {
	//获取第三方应用walletId对应的did
	requesterDid, err := walletId2Did(ds.engine, walletId)
	if err != nil {
		return nil, err
	}
	//验证请求者授权
	secret, encryptSecret, encryptType, err := verifyAuth(ds.engine, requesterDid, credId)
	if err != nil {
		return nil, err
	}
	_, credBytes, _, err := getCredBytes(ds.engine, credId)
	if err != nil {
		return nil, err
	}

	encryptedBytes := gm.Sm4Encrypt(secret, credBytes)
	if encryptedBytes == nil {
		return nil, errors.New("failed to encrypt credential")
	}

	credResp := &view.CredentialResp{
		Credential:    hex.EncodeToString(encryptedBytes),
		EncryptSecret: encryptSecret,
		EncryptType:   encryptType,
	}
	return credResp, nil
}

// 验证凭证请求者授权
func verifyAuth(engine *xorm.Engine, requesterDid, credId string) (secret, encryptSecret, encryptType string, err error) {
	//查询授权表
	credGrant := make([]*entity.CredentialGrant, 0)
	if err = engine.Where("grantee = ? and object_id = ?", requesterDid, credId).Find(&credGrant); err != nil {
		return "", "", "", err
	}
	if len(credGrant) == 0 {
		return "", "", "", errors.New("the requester is not authorized")
	}
	//是否过期
	if time.Now().After(credGrant[0].ExpirationDate) {
		return "", "", "", errors.Errorf("authorization has expired, credId:%s, requesterDid:%s", credId, requesterDid)
	}
	return credGrant[0].Secret, credGrant[0].EncryptSecret, credGrant[0].EncryptType, nil
}

// 返回值：凭证状态，凭证bytes，凭证
func getCredBytes(engine *xorm.Engine, credId string) (credValid bool, credBytes []byte, credDetail *model.CredentialDetail, err error) {
	//查询凭证表
	VerCred := new(entity.VerifiableCredential)
	ok, err := engine.Where("id = ?", credId).Get(VerCred)
	if err != nil {
		return false, nil, nil, err
	}
	if !ok {
		return false, nil, nil, errors.Errorf("the credential does not exist, credId :%s", credId)
	}
	if VerCred.Status == constant.CredentialStatusValid {
		credValid = true
	}

	cred := &model.Credential{
		Context:        []string{constant.CredentialContext},
		CptId:          VerCred.CptId,
		Id:             VerCred.Id,
		Type:           VerCred.Type,
		Issuer:         VerCred.IssuerDid,
		IssuanceDate:   VerCred.IssuanceDate,
		ExpirationDate: VerCred.ExpirationDate,
		Revocation: model.VcRevocation{
			Id:   VerCred.RevocationId,
			Type: VerCred.RevocationType,
		},
		Proof: model.VcProof{
			Creator:        VerCred.ProofCreator,
			SignatureValue: VerCred.ProofSignature,
		},
	}

	//cpt type
	switch VerCred.Type {
	case constant.CptTypeLite:
		cred.Type = constant.Lite
	}

	//revocation type
	switch VerCred.RevocationType {
	case constant.TypeSimpleRevocationListV1:
		cred.Revocation.Type = constant.SimpleRevocationListV1
	}

	//Proof Type
	switch VerCred.ProofType {
	case constant.SignTypeSm2p256v1:
		cred.Proof.Type = constant.DefaultKeyAlgo
	}

	credSubject := model.CredentialSubject{}
	err = json.Unmarshal([]byte(VerCred.CredentialSubject), &credSubject)
	if err != nil {
		return false, nil, nil, err
	}
	cred.CredentialSubject = credSubject
	credBytes, err = json.Marshal(cred)

	credDetail = &model.CredentialDetail{
		Credential: cred,
		TxId:       VerCred.TxId,
	}

	if err != nil {
		return false, nil, nil, err
	}
	return credValid, credBytes, credDetail, nil
}

// 不向发证方返回 issuerName, issuerDid
var issuerInfo = func(isIssuer bool, para string) string {
	if !isIssuer {
		return para
	}
	return ""
}

// 组合SQL
func combineSql(statement string, condition *string, para string, args *[]interface{}) {
	*condition = utils.StrJoin(*condition, statement)
	if para != "" {
		*args = append(*args, para)
	}
}

func (ds *DidService) QueryCredentialDetail(walletId, credentialId string) (*model.CredentialDetail, error) {
	err := verifyCredOwner(ds.engine, walletId, credentialId)
	if err != nil {
		return nil, err
	}

	_, _, credDetail, err := getCredBytes(ds.engine, credentialId)
	if err != nil {
		return nil, err
	}
	return credDetail, nil
}

// 验证walletId和请求的凭证id是否匹配
func verifyCredOwner(engine *xorm.Engine, walletId, credentialId string) error {
	//获取walletId对应的所有did
	MultiDid, err := walletId2MultiDid(engine, walletId)
	if err != nil {
		return err
	}
	args := make([]interface{}, 0)
	condition := "("
	combineSql("owner_did = ? ", &condition, MultiDid[0], &args)

	//组合所有did查询
	if len(MultiDid) > 1 {
		for _, did := range MultiDid[1:] {
			combineSql(" or owner_did = ? ", &condition, did, &args)
		}
	}
	combineSql(") and id = ? ", &condition, credentialId, &args)

	var cred entity.VerifiableCredential
	count, err := engine.Where(condition, args...).Count(&cred)
	if err != nil {
		return err
	}
	if count == 0 {
		return errors.Errorf("walletId:%s is not the owner of credentialId:%s", walletId, credentialId)
	}
	return nil
}

func (srv *DidService) GetCredentialByDid(cptId, did string) (*entity.VerifiableCredential, error) {
	var vc entity.VerifiableCredential
	ok, err := srv.engine.Where("cpt_id = ? and owner_did = ? and status = ?", cptId, did, constant.CredentialStatusValid).Get(&vc)
	if err != nil {
		return nil, err
	}
	if !ok {
		log.Warning("vc is not exist")
		return nil, nil
	}
	return &vc, nil
}
